// ****************************************************************************
//	Copyright (c) 2011 Daniel Watman
//	http://www.keycommander.net/
// ****************************************************************************
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//	
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//	
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
// *********************************************************************
//	File: SPItest.c
//	Purpose: Test of SPI port alternatives (SPI/USART with DMA, clone ports)
//	Author: Daniel Watman
//	Created: 2011.03.07
//	Updated: 2011.03.07
//	Version: 0.1
//	Target: Atmel AVR XMEGA 128A1
//	Compiler: AVR-GCC
// *********************************************************************
//	Changes:
//		0.1	* Initial version. DMA works for both USART and SPI (write only)
//			Clone works for data but only at low speed. < 250kHz probably ok
// *********************************************************************
//	Notes:
//		* Clone is not fast enough at 1MHz. Almost ok at 0.5MHz for data only.
//		* Not tried, but I think DMA read and simultaneous read/write can 
//		be achieved with DMA double-buffering (write then read)
// *********************************************************************
//	Timer usage:
//		Timer D1	10 ms tick for misc timing
//	
//	Pin usage:
//	PORTD:
//		0 O	USART D0 SS
//		1 O	USART D0 XCK
//		2 O	USART D0 RX
//		3 O	USART D0 TX
//		4 O	SPI D SS
//		5 O	SPI D MOSI
//		6 O	SPI D MISO
//		7 O	SPI D SCK
//	PORTF:
//		0 O	Clone SS
//		1 O	Clone SCK
//		3 O	Clone TX
//	
//	Interrupts:
//	High Priority
//		?
//	
//	Medium Priority
//		PORTD_INT0	pin change for clone
//	
//	Low Priority
//		SPID_INT	// SPI byte sent
//		USARTD0_TXC	// USART transaction complete
//		TCD1_OVF	timer D1 overflow for general timing


#include <avr/io.h>
#include <stdio.h>	// for debugging
#include <avr/interrupt.h>
#include "types.h"

void init_osc(void);	// initialise oscillator
void init_interrupts(void);	// initialise interrupts and set priorities
void init_timers(void);	// initialise timers

void dma_txbuf(DMA_CH_t *, void *, u08, void *, u08); // DMA transfer setup for sending data to USART

volatile u08 flg;
volatile u16 tick;

volatile u08 buf[8] = {0xAA,0xFF,0x00,0xAA,0xCC,0xF0,0xFF,0xAA}; // test data

int main (void) {

//PIN SETUP
	PORTD.DIRSET = 0x0B;	// pin 0(SS), 1(XCK), 3(TX) to output for USART
	PORTD.OUTSET = 0x01;	// SS high

	PORTD.DIRSET = 0xB0;	// pin 7(SCK), 5MOSI), 4(SS) to output for SPI
	PORTD.OUTSET = 0x10;	// SS high

	PORTF.DIRSET = 0x0B;	// pin 0(SS), 1(XCK), 3(TX) to output for clone
	PORTF.OUTSET = 0x01;	// SS high

	init_osc();			// initialise oscillator
	init_timers();		// initialise timers
	init_interrupts();	// initialise interrupts and set priorities

// setup USART D0 as SPI port
	USARTD0.CTRLC = 0xC0;	// master SPI mode, MSB first, sample on rising edge
	USARTD0.BAUDCTRLA = 0x0F;	// BSEL LSBs baud = f/(2*(BSEL+1)): 0=16MHz, 7=2MHz, F=1MHz
	USARTD0.BAUDCTRLB = 0x00;	// fractional baud 0, BSEL MSBs = 0
	USARTD0.CTRLB = 0x18;	// TX, RX enabled
	USARTD0.CTRLA |= 0x04;	// enable low level interrupt on transmit complete

// setup SPI D
	SPID.CTRL = 0xD2;	// enable SPI, MSB first, master mode, mode 0, SCK=perclk/32 (1MHz, CLK2x=1)
	SPID.INTCTRL = 0x01;// enable low level interrupt

// setup clone interrupts (clone USART: pins D1 and D3)
	PORTD.INTCTRL |= 0x02;	// medium level interrupt for port interrupt 0
	PORTD.PIN1CTRL = 0x02;	// interrupt on falling edges only (USART SCK)
	PORTD.INT0MASK = 0x02;	// enable interrupt 0 on pin D1 (USART SCK)

// setup DMA
	DMA.CTRL |= 0x80;		// enable DMA controller

	tick = 0;
	sei();	// enable AVR interrupts

	// *** MAIN LOOP ***
	while (1) {

		if (flg & 0x01) {	// regular events
			PORTD.OUTCLR = 0x11;
			PORTF.OUTCLR = 0x01;
			//USARTD0.DATA = 0xAA;
			//SPID.DATA = 0xAA;
			dma_txbuf(&DMA.CH0, (u08 *)&USARTD0.DATA, DMA_CH_TRIGSRC_USARTD0_DRE_gc, (u08 *)buf, 8);	// queue for transmission*****
			//dma_txbuf(&DMA.CH0, (u08 *)&SPID.DATA, DMA_CH_TRIGSRC_SPID_gc, (u08 *)buf, 8);	// queue for transmission*****
			//SPID.DATA = 0xAA;
			PORTF.OUTSET = 0x01;
			flg &= ~0x01;	//clear the flag
		}
/*
		if (flg & 0x02) {	//send a status update message?
				flg &= ~0x02;	//clear flag
		}*/

	}
}

// ********** INTERRUPT FUNCTIONS **********

// interrupt from pin D1 edges (USART SCK)
ISR(PORTD_INT0_vect) {
	if (PORTD.IN&0x08) PORTF.OUTSET = 0x02;
	else 				PORTF.OUTCLR = 0x02;
}

// DMA keeps the buffer full, so this interrupt doesn't happen until the
// end of the last transfer. No need to check DMA status
ISR(USARTD0_TXC_vect) {	// USART D0 transmission complete interrupt
	//if ((DMA.STATUS&0x10) == 0)	// if DMA CH0 is not busy (finished)
	PORTD.OUTSET = 0x01;	// set USART D0 SS
}

// this interrupt occurs after every byte so need to check DMA status
// DMA reports finished at last byte, so need to delay by 1
ISR(SPID_INT_vect) {	// USART D0 transmission complete interrupt
	static u08 busy=0, prev=0;

	prev = busy;
	if ((DMA.STATUS&0x10) == 0)	// if DMA CH0 is not busy (finished) and transaction active
		busy = 0;
	else
		busy = 1;

	if ((prev == 0)&&(busy == 0))
		PORTD.OUTSET = 0x10;	// set SPI D SS
}

ISR(TCD1_OVF_vect) {	// general timing
	tick++;
	flg |= 0x01;
}

// ********** INIT FUNCTIONS **********

void init_osc(void) {	// initialise oscillator
//CLOCK SETUP
	// system oscillator
	OSC.CTRL = 0x03;					// enable 2MHz and 32MHz clocks
	while ((OSC.STATUS&0x02) == 0) {}	// wait for 32MHz oscillator startup
	CCP = 0xD8;							// enable change of protected register
	CLK.CTRL = 0x01;					// select 32MHz clock
}


void init_interrupts(void) {	// initialise interrupts and set priorities
//INTERRUPTS
	PMIC.CTRL |= 0x81;		// enable low level interrupts with round-robin scheduling
	PMIC.CTRL |= 0x02;		// enable medium level interrupts

	TCD1.INTCTRLA |= 0x01;		// set timer D1 overflow interrupt to low priority
}

void init_timers(void) {	// initialise timers
//TIMER SETUP

	// timer D1	- 16 ms tick for misc timing
	TCD1.CTRLA = 0x04;			// prescaler /8 to give 4MHz clock for counter
	TCD1.PER = 40000;			// set top value to 10 ms
}

// DMA transfer setup for sending a data buffer to a fixed address (USART/SPI)
void dma_txbuf(DMA_CH_t *dma_ch, void *dest, u08 trigger, void *buf, u08 length) {
	u32 src_addr, dest_addr;

	// ***NOTE*** ONLY WORKS IF ADDRESSES ARE UNDER 64kB limit
	src_addr = (u16)buf;
	dest_addr = (u16)dest;

	dma_ch->ADDRCTRL = 0x10;	// no reload, increment source address, fixed destination
	dma_ch->TRFCNT = (u16)length;	// set no. bytes for block transfer

	dma_ch->SRCADDR0 = (src_addr >> 0*8 ) & 0xFF;	// source is buffer address
	dma_ch->SRCADDR1 = (src_addr >> 1*8 ) & 0xFF;
	dma_ch->SRCADDR2 = (src_addr >> 2*8 ) & 0xFF;

	dma_ch->DESTADDR0 = (dest_addr >> 0*8 ) & 0xFF;	// destination address
	dma_ch->DESTADDR1 = (dest_addr >> 1*8 ) & 0xFF;
	dma_ch->DESTADDR2 = (dest_addr >> 2*8 ) & 0xFF;

	dma_ch->TRIGSRC = trigger;

	// transfer request is not needed if the trigger is buffer empty (USART)
	// but it is needed if the trigger is transaction complete (SPI)
	// including it for both is fine as long as the peripheral is not in use already
	//dma_ch->CTRLA = 0x84;	// enable channel, no repeat, burst=1, single shot mode
	dma_ch->CTRLA = 0x94;	// enable channel, request transfer, no repeat, burst=1, single shot mode
}
